package model
{
    import flash.display.*;
    import flash.events.*;
    import flash.geom.*;
    import flash.utils.*;
    import flash.text.*;

    import model.*;

    public class CellMap extends Sprite
    {
        public static var ROW:int = 10;
        public static var COL:int = 12;
        public static var ERASABLE_LIMIT:int = 4;
        public static var TIMER_DELAY:int = 350;
        public static var STEP:int = 6;
        public static var DX0:Array = new Array( 0, -1,  0, 1, 1,  1);
        public static var DY0:Array = new Array(-1,  0,  1, 1, 0, -1);
        public static var DX1:Array = new Array(-1, -1, -1, 0, 1,  0);
        public static var DY1:Array = new Array(-1,  0,  1, 1, 0, -1);

        public var score:Score;

        private var cells:Array;
        private var movingCell:Cell;
        private var swapTimer:Timer;
        private var eraseTimer:Timer;
        private var dropTimer:Timer;
        private var traceOut:TextField;

        public function CellMap()
        {
            x = Cell.WIDTH;
            y = Cell.HEIGHT;
            score = new Score(420, 230);
            addChild(score);
            cells = new Array();
            eraseTimer = new Timer(CellMap.TIMER_DELAY, 1);
            eraseTimer.addEventListener(TimerEvent.TIMER, animateErase);
            dropTimer = new Timer(CellMap.TIMER_DELAY, 1);
            dropTimer.addEventListener(TimerEvent.TIMER, animateDrop);
            swapTimer = new Timer(CellMap.TIMER_DELAY, 1);
            swapTimer.addEventListener(TimerEvent.TIMER, animateSwap);
            for(var j:int=0; j<CellMap.ROW; j++)
            {
                var l:int;
                if((j&1) == 0) l = CellMap.COL;
                else l = CellMap.COL + 1;

                for(var i:int=0; i<l; i++)
                {
                    var cell:Cell;
                    var t:int = int(Cell.TYPE_NUM * Math.random());
                    if((j&1) == 0)
                    {
                        cell = new Cell(t, i, j, Cell.WIDTH*(i+0.5), 0.75*Cell.HEIGHT*j);
                    }
                    else
                    {
                        cell = new Cell(t, i, j, Cell.WIDTH*i, 0.75*Cell.HEIGHT*j);
                    }
                    cells.push(cell);
                    addChild(cell);
                }
            }
            addEventListener(MouseEvent.MOUSE_UP, mouseUp);
            addEventListener(MouseEvent.MOUSE_OUT, mouseOut);

            traceOut = new TextField();
            traceOut.text = "Score: " + score.score.toString();
            traceOut.textColor = 0xff0000;
            traceOut.x = 0;
            traceOut.y = 250;
            traceOut.width = 400;
            traceOut.height = 50;
            addChild(traceOut);
            this.checkCells();
        }

        public function display():void
        {
            this.drawBackground();
            this.drawCells();
            score.display();
            trace(">> display");
        }

        public function drawBackground(color:uint=0x000000):void
        {
            graphics.clear();
            graphics.beginFill(color, 1);
            graphics.drawRect(-Cell.WIDTH, -Cell.HEIGHT, 640, 480);
            graphics.endFill();
        }

        public function drawCells():void
        {
            for(var i:int=0; i<cells.length; i++)
            {
                cells[i].display();
            }
        }

        public function getCell(col:int, row:int):Cell
        {
            if(row >= CellMap.ROW || row < 0) return null;
            if((row&1) == 1)
            {
                if(col >= CellMap.COL + 1 || col < 0) return null;
            }
            else
            {
                if(col >= CellMap.COL || col < 0) return null;
            }
            var sum:int = (2*CellMap.COL + 1)*int(row/2) + col;
            if((row&1) == 1)
            {
                sum += CellMap.COL;
            }
            return cells[sum];
        }

        public function createCells():int
        {
            var sum:int = 0;
            for(var i:int=0; i<CellMap.COL; i++)
            {
                if(cells[i].type == Cell.EMPTY)
                {
                    cells[i].type = Cell.TYPE_NUM * Math.random();
                    sum ++;
                }
            }
            return sum;
        }

        public function dropCells():int
        {
            var sum:int = 0;
            for(var i:int=cells.length-1; i>=CellMap.COL; i--)
            {
                if(cells[i].type == Cell.EMPTY)
                {
                    sum ++;
                    var col:int = cells[i].col;
                    var row:int = cells[i].row;
                    var cell:Cell = null;
                    var n:int = CellMap.STEP - 1;
                    if((row&1) == 0)
                    {
                        col += CellMap.DX0[0];
                        row += CellMap.DY0[0];
                    }
                    else
                    {
                        col += CellMap.DX1[n];
                        row += CellMap.DY1[n];
                    }
                    cell = this.getCell(col, row);
                    if((cell != null) && (cell.type != Cell.EMPTY))
                    {
                        cell.move(cells[i]);
                        cells[i].move(cell);
                        continue;
                    }

                    col = cells[i].col;
                    row = cells[i].row;
                    if((row&1) == 0)
                    {
                        col += CellMap.DX0[n];
                        row += CellMap.DY0[n];
                    }
                    else
                    {
                        col += CellMap.DX1[0];
                        row += CellMap.DY1[0];
                    }
                    cell = this.getCell(col, row);
                    if((cell != null) && (cell.type != Cell.EMPTY))
                    {
                        cell.move(cells[i]);
                        cells[i].move(cell);
                        continue;
                    }
                }
            }
            return sum;
        }

        public function isAdjacent(a:Cell, b:Cell):Boolean
        {
            if((a == null) || (b == null)) return false;
            for(var i:int=0; i<CellMap.STEP; i++)
            {
                if((a.row&1) == 0)
                {
                    if((a.col + CellMap.DX0[i] == b.col) &&
                       (a.row + CellMap.DY0[i] == b.row)) return true;
                }
                else
                {
                    if((a.col + CellMap.DX1[i] == b.col) &&
                       (a.row + CellMap.DY1[i] == b.row)) return true;
                }
            }
            return false;
        }

        public function isErasable(a:Cell):Boolean
        {
            if(a == null) return false;
            for(var i:int=0; i<CellMap.STEP/2; i++)
            {
                var col:int = a.col;
                var row:int = a.row;
                var sum:int = 0;
                var cell:Cell = this.getCell(col, row);
                while((cell != null) && (cell.type == a.type))
                {
                    sum ++;
                    if((row&1) == 0)
                    {
                        col += CellMap.DX0[i];
                        row += CellMap.DY0[i];
                    }
                    else
                    {
                        col += CellMap.DX1[i];
                        row += CellMap.DY1[i];
                    }
                    if(sum >= CellMap.ERASABLE_LIMIT) return true;
                    cell = this.getCell(col, row);
                }
                col = a.col;
                row = a.row;
                cell = this.getCell(col, row);
                sum --;
                while((cell != null) && (cell.type == a.type))
                {
                    sum ++;
                    if((row&1) == 0)
                    {
                        col += CellMap.DX0[i + CellMap.STEP/2];
                        row += CellMap.DY0[i + CellMap.STEP/2];
                    }
                    else
                    {
                        col += CellMap.DX1[i + CellMap.STEP/2];
                        row += CellMap.DY1[i + CellMap.STEP/2];
                    }
                    if(sum >= CellMap.ERASABLE_LIMIT) return true;
                    cell = this.getCell(col, row);
                }
            }
            return false;
        }

        public function erase(a:Cell):int
        {
            var eraseList:Array = new Array();
            var sum:int = 0;
            if((a.type != Cell.EMPTY) && isErasable(a))
            {
                eraseList.push(a);
                a.erased = true;
            }
            var l:int = 0;
            while(l < eraseList.length)
            {
                for(var i:int=0; i<CellMap.STEP; i++)
                {
                    var col:int = eraseList[l].col;
                    var row:int = eraseList[l].row;
                    if((row&1) == 0)
                    {
                        col += CellMap.DX0[i];
                        row += CellMap.DY0[i];
                    }
                    else
                    {
                        col += CellMap.DX1[i];
                        row += CellMap.DY1[i];
                    }
                    var cell:Cell = this.getCell(col, row);
                    if(this.isErasable(cell))
                    {
                        if(!cell.erased)
                        {
                            cell.erased = true;
                            eraseList.push(cell);
                        }
                    }
                }
                l ++;
            }
            if(eraseList.length >= CellMap.ERASABLE_LIMIT)
            {
                score.score += 100*(eraseList.length - CellMap.ERASABLE_LIMIT + 1);
                sum = eraseList.length;
            }
            for(i=eraseList.length-1; i>=0; i--)
            {
                eraseList[i].erased = false;
                eraseList[i].type = Cell.EMPTY;
                eraseList.pop();
            }
            return sum;
        }

        public function eraseCells():int
        {
            var sum:int = 0;
            for(var i:int=cells.length-1; i>=0; i--)
            {
                sum += this.erase(cells[i]);
            }
            return sum;
        }

        public function isSwappable(a:Cell, b:Cell):Boolean
        {
            if((a == null) || (b == null)) return false;
            var swappable:Boolean = false;
            if(this.isAdjacent(a, b))
            {
                this.swapCells(a, b);
                if(isErasable(a)) swappable = true;
                else if(isErasable(b)) swappable = true;
                this.swapCells(a, b);
            }
            return swappable;
        }

        public function swapCells(a:Cell, b:Cell):void
        {
            var temp:int = a.type;
            a.type = b.type;
            b.type = temp;
        }

        public function swapCellsAnimate():void
        {
            Cell.LOCKED = true;
            Cell.SELECTED_CELL.move(Cell.OVER_CELL);
            Cell.OVER_CELL.move(Cell.SELECTED_CELL);
            swapTimer.start();
        }

        private function animateSwap(e:TimerEvent):void
        {
            swapTimer.stop();
            swapTimer.reset();
            trace("Error: doErase");
            this.doErase();
        }

        public function checkCells():void
        {
            for(var i:int=cells.length-1; i>=0; i--)
            {
                while(isErasable(cells[i]))
                {
                    cells[i].type = Cell.TYPE_NUM * Math.random();
                }
            }
            /*
            for(i=0; i<4; i++)
            {
                cells[int((cells.length-1) * Math.random())].type = Cell.STONE;
            }*/
        }

        public function doErase():void
        {
            eraseTimer.start();
        }

        private function animateErase(e:TimerEvent):void
        {
            eraseTimer.stop();
            eraseTimer.reset();
            if(this.eraseCells() > 0)
            {
                traceOut.text = "Score: " + score.score.toString();
                dropTimer.start();
            }
            else
            {
                Cell.LOCKED = false;
            }
            e.updateAfterEvent();
        }

        private function animateDrop(e:TimerEvent):void
        {
            dropTimer.stop();
            dropTimer.reset();
            var c:int = this.createCells();
            var d:int = this.dropCells();
            if((d > 0) || (c > 0))
            {
                dropTimer.start();
            }
            else
            {
                this.doErase();
            }
            e.updateAfterEvent();
        }

        private function mouseOut(e:MouseEvent):void
        {
            if(!Cell.LOCKED)
            {
                if(Cell.OVER_CELL != null)
                {
                    Cell.OVER_CELL.overed = false;
                    Cell.OVER_CELL = null;
                }
            }
        }

        private function mouseUp(e:MouseEvent):void
        {
            if(Cell.SELECTED_CELL != null)
            {
                if(Cell.OVER_CELL != null && Cell.SELECTED_CELL != Cell.OVER_CELL)
                {
                    if(this.isSwappable(Cell.SELECTED_CELL, Cell.OVER_CELL))
                    {
                        Cell.SELECTED_CELL.selected = false;
                        Cell.OVER_CELL.overed = false;
                        this.swapCellsAnimate();
                    }
                }
            }
        }
    }
}
